// Copyright 2011, Vanya Davidenko.
// Используемая кодировка: UTF-8.
 
#ifndef CHVI_CAM_CONTROL_H_
#define CHVI_CAM_CONTROL_H_

#ifndef _WIN32
# error "This header should be used in Windows only"
#endif 

#include <videoInput.h>
#include <Dshow.h>
#include <string>
#include <stdexcept>



namespace cvhi {

/// Исключение, генерируемое методами класса CamControl.
class CamControlException : public ::std::exception {
  public:
    CamControlException(const ::std::string& msg) 
        : ::std::exception(msg.c_str()) {}
};



/// Управление веб-камерой, декоратор над videoInput.
class CamControl {
  public:
    /// Структура, возвращающая свойства некоторого параметра камеры.
    struct Settings {
        /// Текущее значение.
        long cur;

        /// Минимальное значение.
        long min;

        /// Максимальное значение.
        long max;

        /// Шаг изменения значений.
        long step;

        /// Значение по умолчанию.
        long default;

        /// Набор флагов.
        long flags;
    };


    /// Вывод в консоль номеров веб-камер, подключенных к системе.
    /// @return число устройств.
    static int listDevices() {
        return videoInput().listDevices();
    }


    /// Конструктор.
    /// @param[in] cam_id номер камеры
    /// @param[in] width предпочтительная ширина кадра
    /// @param[in] height предпочтительная высота кадра
    /// @param[in] fps предпочтительное число кадров в секунду
    /// @param[in] flip_red_blue флаг, менять ли местами красную и синюю
    ///            составляющую пикселов кадра
    /// @param[in] flip_image флаг, переворачивать ли кадр на 180 градусов
    /// @param[in] connection тип соединения (см. videoInput)
    /// @param[in] verbose включение отладочного вывода.
    /// @except CamControlException.
    CamControl(const int cam_id,
               const int width,
               const int height,
               const int fps,
               const bool flip_red_blue = false,
               const bool flip_image = true,
               const int connection = VI_COMPOSITE,
               const bool verbose = true)
        : cam_id_(cam_id),
          flip_red_blue_(flip_red_blue), 
          flip_image_(flip_image) {
        vi_.setVerbose(verbose);
        vi_.setIdealFramerate(cam_id_, fps);
        vi_.setAutoReconnectOnFreeze(cam_id_, true, 100);
        if ( !vi_.setupDevice(cam_id_, width, height, connection) ) {
            throw CamControlException("setupDevice failed");
        }
    }

    ~CamControl() {
        vi_.stopDevice(cam_id_); 
    }


    /// Перезагрузка камеры.
    /// @except CamControlException.
    void restart() {
        if ( !vi_.restartDevice(cam_id_) ) {
            throw CamControlException("restartDevice failed");
        }
    }

    /// Задание формата изображения (PAL, NTSC) (см. videoInput).
    /// @except CamControlException.
    void setFormat(const int format) {
        if ( !vi_.setFormat(cam_id_, format) ) {
            throw CamControlException("setFormat failed");
        }
    }

    /// Имя камеры.
    ::std::string name() const {
        return vi_.getDeviceName(cam_id_);
    }


    /// Проверка поступления нового кадра.
    /// Метод необходимо вызывать каждый цикл приложения, иначе возможно
    /// зависание камеры в связи с отсутствием запросов.
    bool isFrameNew() const {
        return vi_.isFrameNew(cam_id_); 
    }

    /// Получение буфера кадра.
    unsigned char* getPixels() const {
        return vi_.getPixels(cam_id_, flip_red_blue_, flip_image_);
    }

    /// Запись кадра в переданный буфер.
    /// @pre Размер буфера должен быть не меньше size().
    void getPixels(unsigned char* pixels) const {
        if ( !vi_.getPixels(cam_id_, pixels, flip_red_blue_, flip_image_) ) {
            throw CamControlException("getPixels failed");
        }
    }

    /// Ширина кадра.
    /// Возвращаемое значение может не совпадать с переданным в конструкторе,
    /// так как это фактическое значение, а не желательное.
    int width() const {
        return vi_.getWidth(cam_id_);
    }

    /// Высота кадра.
    /// Возвращаемое значение может не совпадать с переданным в конструкторе,
    /// так как это фактическое значение, а не желательное.
    int height() const {
        return vi_.getHeight(cam_id_);
    }

    /// Размер буфера кадра.
    int size() const {
        return vi_.getSize(cam_id_);
    }


    /// Вызов окна с настройками камеры.
    void showSettingsWindow() {
        vi_.showSettingsWindow(cam_id_);
    }


    /// Получение настроек камеры.
    /// @param[in] property параметр, по которому получаются настройки. 
    ///   см. http://msdn.microsoft.com/en-us/library/dd318253(v=vs.85).aspx
    Settings getSettings(const CameraControlProperty property) const {
        Settings result = { 0 };
        if ( !vi_.getVideoSettingCamera(cam_id_, property, 
                                        result.min, result.max, 
                                        result.step, result.cur, 
                                        result.flags, result.default) ) {
            throw CamControlException("getVideoSettingCamera failed");
        }
        return result;
    }

    /// Задание настроек камеры.
    /// @param[in] property параметр, по которому задаются настройки. 
    ///   см. http://msdn.microsoft.com/en-us/library/dd318253(v=vs.85).aspx
    void setSettings(const CameraControlProperty property, const int value) {
        if ( !vi_.setVideoSettingCamera(cam_id_, property, value,
                                        CameraControl_Flags_Manual) ) {
            throw CamControlException("setVideoSettingCamera failed");
        }
    }

    /// Переключение автоматической смены настроек камеры.
    /// @param[in] property параметр, по которому задаются настройки
    ///   см. http://msdn.microsoft.com/en-us/library/dd318253(v=vs.85).aspx
    /// @param[in] on переключающий флаг (true — включить, false — выключить).
    void setAutoSettings(const CameraControlProperty property, const bool on) {
        const long flag = on ? CameraControl_Flags_Auto :
                               CameraControl_Flags_Manual;

        if ( !vi_.setVideoSettingCamera(cam_id_, property, 
                                        getSettings(property).cur, flag) ) {
            throw CamControlException("setVideoSettingCamera failed");
        }
    }


    /// Получение настроек изображения.
    /// @param[in] property параметр, по которому получаются настройки. 
    ///   см. http://msdn.microsoft.com/en-us/library/dd407328(v=VS.85).aspx
    Settings getSettings(const VideoProcAmpProperty property) const {
        Settings result = { 0 };
        if ( !vi_.getVideoSettingFilter(cam_id_, property, 
                                        result.min, result.max, 
                                        result.step, result.cur, 
                                        result.flags, result.default) ) {
            throw CamControlException("getVideoSettingCamera failed");
        }
        return result;
    }

    /// Задание настроек изображения.
    /// @param[in] property параметр, по которому задаются настройки. 
    ///   см. http://msdn.microsoft.com/en-us/library/dd407328(v=VS.85).aspx
    void setSettings(const VideoProcAmpProperty property, const int value) {
        if ( !vi_.setVideoSettingFilter(cam_id_, property, value,
                                        VideoProcAmp_Flags_Manual) ) {
            throw CamControlException("setVideoSettingCamera failed");
        }
    }

    /// Переключение автоматической смены настроек изображения.
    /// @param[in] property параметр, по которому задаются настройки
    ///   см. http://msdn.microsoft.com/en-us/library/dd407328(v=VS.85).aspx
    /// @param[in] on переключающий флаг (true — включить, false — выключить).
    void setAutoSettings(const VideoProcAmpProperty property, const bool on) {
        const long flag = on ? VideoProcAmp_Flags_Auto :
                               VideoProcAmp_Flags_Manual;

        if ( !vi_.setVideoSettingFilter(cam_id_, property, 
                                        getSettings(property).cur, flag) ) {
            throw CamControlException("setVideoSettingCamera failed");
        }
    }


    /// Получение настроек фокусировки.
    /// @except CamControlException.
    Settings getFocusSettings() const {
        return getSettings(CameraControl_Focus);
    }

    /// Изменение фокусного расстояния. 
    /// Допустимые значения могут быть получены из getFocusSettings().
    /// @except CamControlException.
    void setFocus(const int value) {
        setSettings(CameraControl_Focus, value);
    }

    /// Включение/выключение автофокуса.
    /// @except CamControlException.
    void setAutoFocus(const bool on) {
        setAutoSettings(CameraControl_Focus, on);
    }


    /// Получение настроек экспозиции.
    /// @except CamControlException.
    Settings getExposureSettings() const {
        return getSettings(CameraControl_Exposure);
    }

    /// Изменение экспозиции. 
    /// Допустимые значения могут быть получены из getExposureSettings().
    /// @except CamControlException.
    void setExposure(const int value) {
        setSettings(CameraControl_Exposure, value);
    }

    /// Включение/выключение автоэкспозиции.
    /// @except CamControlException.
    void setAutoExposure(const bool on) {
        setAutoSettings(CameraControl_Exposure, on);
    }


    /// Получение свойств увеличения.
    /// @except CamControlException.
    Settings getZoomSettings() const {
        return getSettings(CameraControl_Zoom);
    }

    /// Изменение увеличения. 
    /// Допустимые значения могут быть получены из getExposureSettings().
    /// обычно от 10 до 600 мм.
    /// @except CamControlException.
    void setZoom(const int value) {
        setSettings(CameraControl_Zoom, value);
    }
        

  private:
    // Класс videoInput полностью игнорирует константность.
    mutable videoInput vi_;
    /* const */ int cam_id_;
    /* const */ bool flip_red_blue_;
    /* const */ bool flip_image_;
};

}  // cvhi

#endif  // CHVI_CAM_CONTROL_H_
